/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2022. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <sys/file.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include "logger.h"
#define FILE_PERMISSION           0600
#define TIMESTAMP_SHIFT_60        60
#define TIMESTAMP_SHIFT_3600      3600
#define TIMESTAMP_SHIFT_1000      1000
#define TIMESTAMP_SHIFT_100       100
#define TIMESTAMP_SHIFT_10        10
#define TIMESTAMP_PRE             6

static logger_t *g_logger;
static const int tenPowers[6] = {1, 10, 100, 1000, 10000, 100000};
static logger_level_t logLevelPrintArr[] = {
    {"info", LOG_INFO},
    {"debug", LOG_DEBUG},
    {"err", LOG_ERR},
    {"warning", LOG_WARNING},
    {NULL, -1}
};

static void roller_unlock(roller_t *roller)
{
    if (roller->roller_lock_fd >= 0) {
        if (flock(roller->roller_lock_fd, LOCK_UN))
            printf("qt enclave logger: roller unlocks err and errno is %d\n", errno);
        close(roller->roller_lock_fd);
        roller->roller_lock_fd = -1;
    }
}

static int roller_flock_timeout(roller_t *roller, int op)
{
    long int count;

    if (roller->lock_timeout < 0)
        return flock(roller->roller_lock_fd, op);

    /* set 60s timeout for waiting for lock */
    count = roller->lock_timeout * QT_ENCLAVE_ROLLER_LOCK_RATIO;
    while (count > 0) {
        if (flock(roller->roller_lock_fd, op | LOCK_NB)) {
            /* sleep 10ms */
            usleep(QT_ENCLAVE_ROLLER_SLEEPTIME);
            count--;
            continue;
        }
        break;
    }
    return count == 0 ? -1 : 0;
}

static int roller_lock(roller_t *roller)
{
    int rc;

    roller->roller_lock_fd = open(roller->roller_lock, O_CREAT | O_RDONLY, FILE_PERMISSION);
    if (roller->roller_lock_fd < 0) {
        printf("qt enclave logger: open roller lock file err and errno is %d\n", errno);
        return -1;
    }
    if (roller_flock_timeout(roller, LOCK_EX)) {
        printf("qt enclave logger: roller locks err\n");
        rc = -1;
        goto close_res;
    }
    rc = 0;
close_res:
    close(roller->roller_lock_fd);
    roller->roller_lock_fd = -1;
    return rc;
}

static void change_to_rfc3339(timestamp_t *timestamp, char *rfc_time_buf, int len)
{
    int index = 0;
    int power;
    int secfrac;
    int digit;
    char *p = rfc_time_buf;

    p[index++] = (timestamp->year / TIMESTAMP_SHIFT_1000) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->year / TIMESTAMP_SHIFT_100) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->year / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->year % TIMESTAMP_SHIFT_10) + '0';
    p[index++] = '-';

    p[index++] = (timestamp->month / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->month % TIMESTAMP_SHIFT_10) + '0';
    p[index++] = '-';

    p[index++] = (timestamp->day / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->day % TIMESTAMP_SHIFT_10) + '0';
    p[index++] = 'T';

    p[index++] = (timestamp->hour / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->hour % TIMESTAMP_SHIFT_10) + '0';
    p[index++] = ':';

    p[index++] = (timestamp->minute / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->minute % TIMESTAMP_SHIFT_10) + '0';
    p[index++] = ':';

    p[index++] = (timestamp->second / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = (timestamp->second % TIMESTAMP_SHIFT_10) + '0';

    if (timestamp->secfracPrecision > 0) {
        p[index++] = '.';
        power = tenPowers[(timestamp->secfracPrecision - 1) % TIMESTAMP_PRE];
        secfrac = timestamp->secfrac;
        while (power > 0) {
            digit = secfrac / power;
            secfrac -= digit * power;
            power /= TIMESTAMP_SHIFT_10;
            p[index++] = digit + '0';
        }
    }
    p[index++] = timestamp->offsetMode;
    p[index++] = (timestamp->offsetHour / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = timestamp->offsetHour % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = ':';
    p[index++] = (timestamp->offsetMinute / TIMESTAMP_SHIFT_10) % TIMESTAMP_SHIFT_10 + '0';
    p[index++] = timestamp->offsetMinute % TIMESTAMP_SHIFT_10 + '0';

    p[index++] = '\0';
}

static int event_get_timestamp(event_t *event)
{
    struct timeval tv;
    struct tm tm_buf;
    time_t secs;
    timestamp_t timestamp;
    long tm_gmtoff;
    char rfc_time_buf[TIMESTAMP_MAX_LEN] = {0};
    int rc;

    gettimeofday(&tv, NULL);
    secs = tv.tv_sec;
    localtime_r(&secs, &tm_buf);

    timestamp.year = tm_buf.tm_year + BEGIN_YEAR;
    timestamp.month = tm_buf.tm_mon + 1;
    timestamp.day = tm_buf.tm_mday;
    timestamp.hour = tm_buf.tm_hour;
    timestamp.minute = tm_buf.tm_min;
    timestamp.second = tm_buf.tm_sec;
    timestamp.secfrac = tv.tv_usec;
    timestamp.secfracPrecision = DEFAULT_SECFRACPRECISION;
    tm_gmtoff = tm_buf.tm_gmtoff;
    if (tm_gmtoff < 0) {
        timestamp.offsetMode = '-';
        tm_gmtoff *= -1;
    } else {
        timestamp.offsetMode = '+';
    }
    timestamp.offsetHour = tm_gmtoff / TIMESTAMP_SHIFT_3600;
    timestamp.offsetMinute = (tm_gmtoff % TIMESTAMP_SHIFT_3600) / TIMESTAMP_SHIFT_60;

    change_to_rfc3339(&timestamp, rfc_time_buf, sizeof(rfc_time_buf));
    rc = snprintf(event->timestamp, sizeof(event->timestamp), "%s", rfc_time_buf);
    if (rc < 0) {
        printf("qt enclave logger: event_get_timestamp performs snprintf err and errno is %d\n", errno);
        return rc;
    }

    return 0;
}

static int event_get_level_name(event_t *event, int level)
{
    logger_level_t *tmp_logger_level;
    int rc;

    for (tmp_logger_level = logLevelPrintArr; tmp_logger_level->level_name; tmp_logger_level++) {
        if ((unsigned int)level == tmp_logger_level->level) {
            rc = snprintf(event->level_name, sizeof(event->level_name), "%s", tmp_logger_level->level_name);
            if (rc < 0) {
                printf("qt enclave logger: event_get_level_name performs snprintf err and errno is %d\n", errno);
                return rc;
            }
            return 0;
        }
    }
    return -1;
}

static void roller_del(roller_t *roller)
{
    if (roller->roller_lock_fd >= 0)
        close(roller->roller_lock_fd);
    free(roller);
}

static int roller_init(roller_t *roller)
{
    int rc;

    roller->max_cnt = QT_ENCLAVE_LOG_MAX_CNT;
    roller->size_limit = QT_ENCLAVE_LOG_SIZE;
    roller->lock_timeout = QT_ENCLAVE_ROLLER_LOCK_TIMEOUT;
    roller->roller_lock_fd = -1;
    rc = snprintf(roller->roller_lock, sizeof(roller->roller_lock), "%s", QT_ENCLAVE_LOG_ROLLER_LOCK);
    if (rc < 0) {
        printf("qt enclave logger: roller_init performs snprintf err and errno is %d\n", errno);
        return rc;
    }
    return 0;
}

static int logger_open_log_file(logger_t *logger)
{
    int fd;

    if (access(logger->logger_file, F_OK) == 0 &&
        chown(logger->logger_file, geteuid(), getegid())) {
            printf("qt enclave logger: log file exists but has no permission and errno is %d\n", errno);
            return -1;
    }
    fd = open(logger->logger_file, O_APPEND | O_CREAT | O_WRONLY, FILE_PERMISSION);
    if (fd < 0) {
        printf("qt enclave logger: can not open log file and errno is %d \n", errno);
        return -1;
    }
    logger->logfile_fd = fdopen(fd, "ab");
    if (!logger->logfile_fd) {
        close(fd);
        printf("qt enclave logger: convert to file description err and errno is %d \n", errno);
        return -1;
    }
    return 0;
}

void logger_del()
{
    if (!g_logger) {
        printf("qt enclave logger: double free logger\n");
        return;
    }

    if (g_logger->roller)
        roller_del(g_logger->roller);
    if (g_logger->logfile_fd)
        fclose(g_logger->logfile_fd);
    free(g_logger);
    g_logger = NULL;
}

int logger_init()
{
    int rc = 0;

    if (g_logger) {
        return 0;
    }
    g_logger = calloc(1, sizeof(logger_t));
    if (!g_logger) {
        printf("qt enclave logger: calloc memory for g_logger err\n");
        return -1;
    }
    rc = snprintf(g_logger->logger_file, sizeof(g_logger->logger_file), "%s", QT_ENCLAVE_LOG_FILE);
    if (rc < 0) {
        printf("qt enclave logger: g_logger->logger_file init err and errno is %d\n", errno);
        goto err_return;
    }
    rc = logger_open_log_file(g_logger);
    if (rc < 0) {
        printf("qt enclave logger: open log file err \n");
        goto err_return;
    }
    g_logger->roller = calloc(1, sizeof(roller_t));
    if (!g_logger->roller) {
        printf("qt enclave logger: calloc memory for roller err\n");
        rc = -1;
        goto err_return;
    }

    rc = roller_init(g_logger->roller);
    if (rc < 0) {
        printf("qt enclave logger: roller_init err\n");
        goto err_return;
    }

    return rc;
err_return:
    logger_del();
    return rc;
}

static int logger_roller_process(logger_t *logger)
{
    char oldfile[PATH_MAX + 1] = {0};
    char newfile[PATH_MAX + 1] = {0};
    int rc = 0;
    int i;

    for (i = logger->roller->max_cnt - 1; i > 0; i--) {
        rc = snprintf(oldfile, PATH_MAX + 1, "%s.%1d", logger->logger_file, i);
        if (rc < 0) {
            printf("qt enclave logger: archive log file name is truncated and errno is %d\n", errno);
            return -1;
        }
        if (access(oldfile, F_OK))
            continue;
        rc = snprintf(newfile, PATH_MAX + 1, "%s.%1d", logger->logger_file, i + 1);
        if (rc < 0) {
            printf("qt enclave logger: archive log file name is truncated and errno is %d\n", errno);
            return -1;
        }
        if (rename(oldfile, newfile)) {
            printf("qt enclave logger: rename log file %d err and errno is %d\n", i, errno);
            return -1;
        }
    }
    if (fclose(logger->logfile_fd)) {
        printf("qt enclave logger: close log file err, while rolling logs and errno is %d\n", errno);
        return -1;
    }

    if (rename(logger->logger_file, oldfile)) {
        printf("qt enclave logger: rename the base_log_file err and errno is %d\n", errno);
        return -1;
    }

    return logger_open_log_file(logger);
}

static int logger_roller_checkpoint(logger_t *logger)
{
    struct stat log_stat;
    int rc;

    if (logger == NULL) {
        printf("qt enclave logger: please init logger firstly\n");
        return -1;
    }
    if (roller_lock(logger->roller) < 0) {
        printf("qt enclave logger: roller lock err\n");
        return -1;
    }
    if (stat(logger->logger_file, &log_stat)) {
        printf("qt enclave logger: perform stat on the log file err \n");
        rc = -1;
        goto release_lock;
    }

    if ((unsigned long)(log_stat.st_size) < logger->roller->size_limit) {
        /* the log file is not large enough and no need to perform log rotation */
        rc = 0;
        goto release_lock;
    }

    if (logger_roller_process(logger)) {
        printf("qt enclave logger: log file rolling err\n");
        rc = -1;
        goto release_lock;
    }
    rc = 0;
release_lock:
    roller_unlock(logger->roller);
    return rc;
}

static void enclave_log_print_to_stream(FILE *logfile_fd, char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    (void)vfprintf(logfile_fd, fmt, ap);
    va_end(ap);
    (void)fflush(logfile_fd);
}

static void enclave_log_print_log_entity(event_t *event, const char *funcname, int linenr, int level)
{
    int rc;

    rc = logger_roller_checkpoint(g_logger);
    if (rc < 0) {
        printf("qt enclave logger: checkpoint err and lose a log entity:\n");
        enclave_log_print_to_stream(stderr, QT_ENCLAVE_LOG_FORMAT, event->timestamp, event->level_name,
                                    QT_ENCLAVE_BIN, getpid(), funcname, linenr, event->buf);
        return;
    }
    /* write log entity into log file */
    enclave_log_print_to_stream(g_logger->logfile_fd, QT_ENCLAVE_LOG_FORMAT, event->timestamp, event->level_name,
                                QT_ENCLAVE_BIN, getpid(), funcname, linenr, event->buf);
}

void enclave_log(int level, const char *funcname, int linenr, const char *fmt, ...)
{
    int rc;
    va_list ap;
    event_t event;

    rc = event_get_level_name(&event, level);
    if (rc < 0) {
        printf("qt enclave logger: get level err\n");
        return;
    }
    rc = event_get_timestamp(&event);
    if (rc < 0) {
        printf("qt enclave logger: get timestamp err \n");
        return;
    }
    /* fillout event.buf */
    memset(event.buf, '\0', sizeof(event.buf));
    va_start(ap, fmt);
    /* the maximum length of string written to the event->buf is LOG_BUF_MAX_LEN - 1 */
    /* vsnprintf guarantees that event->buf[LOG_BUF_MAX_LEN - 1] = '\0' */
    rc = vsnprintf(event.buf, LOG_BUF_MAX_LEN, fmt, ap);
    va_end(ap);
    if (rc < 0) {
        printf("qt enclave logger: enclave_log performs vsnprintf err and errno is %d\n", errno);
        return;
    } else if (rc >= LOG_BUF_MAX_LEN) {
        /* the length of the event->buf is (LOG_BUF_MAX_LEN + 1) */
        /* event->buf[LOG_BUF_MAX_LEN] = '\0' */
        printf("qt enclave logger: log entity %s[%d] is truncated\n", funcname, linenr);
        event.buf[LOG_BUF_MAX_LEN - 1] = '\n';
    } else {
        /* rc is the length of written string without \0 */
        /* event->buf[rc-1] is the last valid written character */
        /* add the \n at the end of the string if it does not contain the \n */
        /* the rest of buf is '\0' because we use memset in advance */
        if (event.buf[rc - 1] != '\n')
            event.buf[rc] = '\n';
    }
    enclave_log_print_log_entity(&event, funcname, linenr, level);
}